﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using ResourceBlender.Core;

namespace ResourceBlender.DAL.Import
{
    public class ResxResourceImporter : ResourceImporterBase
    {

        /// <summary>
        /// Imports from .NET managed resource (.resx) files.
        /// </summary>
        /// <param name="log">A delegate to which log message should be sent.</param>
        /// <param name="files">The files to import.</param>
        /// <param name="application">The application into which to import the bundles.</param>
        /// <param name="appendLanguages"><c>true</c> if languages not currently used by the application should be added, otherwise false.</param>
        /// <param name="treatNoExtAsDefaultLang"><c>true</c> if files with no language code extension should be treated as the default language.</param>
        /// <param name="nameFilter">If not <c>null</c>, only elements whose name conatins this string will be imported.</param>
        public static void Import(StatusMessage.StatusChangedEventHandler log, Dictionary<string, string> files, RBApplication application, bool appendLanguages, bool treatNoExtAsDefaultLang, string nameFilter)
        {
            Dictionary<string, Dictionary<string, RBElement>> bundles = new Dictionary<string, Dictionary<string, RBElement>>(); // <bundle, <elementname<eleData>>>
            string bundleName, language, elementName, translation, comment, dataType;
            XmlAttributeCollection attr;
            int splitter;
            Dictionary<string, RBElement> currentBundle;
            log(new StatusMessage(I18nUtils.GetString("UIStrings", "ParsingBundles"), StatusMessage.StatusMessageType.Status));
            List<string> newLanguages = new List<string>();
            foreach (KeyValuePair<string, string> file in files)
            { // <filename, UTF8 contents>

                bool hasLanguage = true; // no language in filename, treat comments as element comments, not translator comments                

                CultureInfo cultureFromFileName = GetCultureFromFileName(file.Key, out bundleName);

                if(cultureFromFileName == null)
                {
                    if (treatNoExtAsDefaultLang && !files.ContainsKey(string.Format("{0}.{1}.resx", bundleName, application.DefaultLanguage)))
                    { // if the default language for this file is included in the zip, use it instead of this file for the default language
                        language = application.DefaultLanguage;
                        hasLanguage = false;
                    }
                    else
                    {
                        continue;
                    }
                }
                else
                {
                    language = cultureFromFileName.Name;                    
                }
                
                //TODO: Consider to use the current standard of chinese cultures as specified in
                //http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx
                switch (language)
                { // the GetCultures() method lists them as this, so if they're imported as Hans and Hant, they don't match up.
                    case "zh-Hans":
                        language = "zh-CHS";
                        break;
                    case "zh-Hant":
                        language = "zh-CHT";
                        break;
                }

                if (appendLanguages && !application.Languages.Contains(language) && !newLanguages.Contains(language))
                {
                    newLanguages.Add(language);
                }
                if (bundles.ContainsKey(bundleName))
                {
                    currentBundle = bundles[bundleName];
                }
                else
                {
                    currentBundle = new Dictionary<string, RBElement>();
                    bundles.Add(bundleName, currentBundle);
                }
                XmlDocument doc = new XmlDocument();
                try
                {
                    doc.LoadXml(file.Value);
                }
                catch (Exception ex)
                {
                    log(new StatusMessage("Invalid XML file: " + file.Key + " - " + ex.Message, StatusMessage.StatusMessageType.Error));
                    continue;
                }
                if (doc.ChildNodes.Count == 0)
                {
                    log(new StatusMessage("Invalid resx file: " + file.Key, StatusMessage.StatusMessageType.Error));
                    continue;
                }
                XmlNodeList nodes = doc.SelectNodes("/root/data[count(value)=1]");
                foreach (XmlNode node in nodes)
                {
                    if (node.NodeType == XmlNodeType.Element)
                    {
                        dataType = null;
                        attr = node.Attributes;
                        if (attr["type"] != null)
                        {
                            dataType = attr["type"].Value;
                        }
                        else if (attr["mimetype"] != null)
                        {
                            dataType = attr["mimetype"].Value;
                        }
                        if (attr["name"] == null)
                        {
                            log(new StatusMessage(string.Format("Invalid element in file {0}: name attribute missing", file.Key), StatusMessage.StatusMessageType.Error));
                            continue;
                        }
                        else
                        {
                            elementName = attr["name"].Value;
                        }
                        if (nameFilter != null && !elementName.Contains(nameFilter))
                        {
                            log(new StatusMessage(string.Format("Element '{0}' excluded by filter, skipping...", elementName), StatusMessage.StatusMessageType.Status));
                            continue;
                        }
                        translation = string.Empty;
                        comment = string.Empty;
                        foreach (XmlNode child in node)
                        {
                            if (child.NodeType == XmlNodeType.Element)
                            {
                                if (child.Name == "value")
                                {
                                    translation = child.InnerText;
                                }
                                else if (child.Name == "comment")
                                {
                                    comment = child.InnerText;
                                }
                            }
                        }
                        if (translation.Length == 0)
                        {
                            continue;
                        }
                        switch (dataType)
                        {
                            case null: // not set, resource is a string
                                RBTranslation trans = new RBTranslation(language, translation, "");
                                if (currentBundle.ContainsKey(elementName))
                                { // some translations already added for this element
                                    if (comment.Length > 0)
                                    { // this is the element comment, not the translator commentl
                                        if (hasLanguage)
                                        {
                                            trans.Comment = comment;
                                        }
                                        else
                                        {
                                            currentBundle[elementName].Resource.Comment = comment;
                                        }
                                    }
                                    if (!currentBundle[elementName].Resource.Translations.ContainsKey(language))
                                    { // translation not added yet
                                        currentBundle[elementName].Resource.Translations.Add(language, trans);
                                    }
                                }
                                else
                                { // element not added yet
                                    RBElement element = new RBElement();
                                    element.Name = elementName;
                                    element.BundleName = bundleName;
                                    if (comment.Length > 0)
                                    { // this is the element comment, not the translator commentl
                                        if (hasLanguage)
                                        {
                                            trans.Comment = comment;
                                        }
                                        else
                                        {
                                            element.Resource.Comment = comment;
                                        }
                                    }
                                    element.Resource.Translations.Add(language, trans);
                                    currentBundle.Add(elementName, element);
                                }
                                break;
                        }
                    }
                }
            }
            ProcessBundles(bundles, application, log);
            if (newLanguages.Count > 0)
            {
                Database db = null;
                try
                {
                    db = new Database();
                    db.OpenConnection();
                    using (Command cmd = db.CreateCommand())
                    {
                        application.Languages.AddRange(newLanguages);
                        cmd.CommandText = string.Format("UPDATE {0}applications SET languages = @langs WHERE id = @applicationId", db.TablePrefix);
                        cmd.AddParameterWithValue("@langs", Utils.Implode(application.Languages, ",", true));
                        cmd.AddParameterWithValue("@applicationId", application.ID);
                        cmd.ExecuteNonQuery();
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "AddedNumNewLanguages"), newLanguages.Count), StatusMessage.StatusMessageType.Success));
                    }
                }
                catch (Exception ex)
                {
                    log(new StatusMessage(ex.Message, StatusMessage.StatusMessageType.Error));
                }
                finally
                {
                    if (db != null && db.Connected)
                    {
                        try
                        {
                            db.CloseConnection();
                        }
                        catch { }
                    }
                }
            }
        }


        /// <summary>
        /// <para>Returns the culture info from an resx file name</para>
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="bundleName"></param>
        /// <returns></returns>
        public static CultureInfo GetCultureFromFileName(string fileName, out string bundleName)
        {            
            string[] strings = fileName.Split('.');            

            if (strings.Length > 2) // There might be a culture code in the file name
            {
                string cultureName = strings[strings.Length - 2]; // The culture name is always preceeding the file extension

                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);

                try
                {                    
                    var cultureInfo = new CultureInfo(cultureName); // If it is not a valid culture name, an ArgumentException will be thrown

                    // Valid culture name, the bundle name is the file name without culture name and extension
                    bundleName = fileNameWithoutExtension.Substring(0,
                                                                    fileNameWithoutExtension.Length -
                                                                    cultureInfo.Name.Length - 1);

                    return cultureInfo;
                }
                catch (ArgumentException)
                {
                    // No valid culture name, the bundle name is the file name without extension
                    bundleName = fileNameWithoutExtension;

                    // Not a valid culture name, return null
                    return null;
                }
            }

            // The file name only contained the name and the extension, return null
            bundleName = strings[0];
            return null;
        }
    }
}
